package extnode

import (
	"errors"
	"net"
	"sync"
)

type ConnPair struct {
	followers [FullReplFactor]*net.TCPConn
	next      *ConnPair
}

func (cp *ConnPair) RelayRequest(req *Packet) (err error) {
	for i := 0; i < FullReplFactor; i++ {
		if err = req.WriteToConn(cp.followers[i]); err != nil {
			break
		}
	}

	if err != nil {
		cp.releaseConn()
	}
	return
}

func (cp *ConnPair) FetchReply() (err error) {
	reply := NewPacket()
	for i := 0; i < FullReplFactor; i++ {
		err = reply.ReadFromConn(cp.followers[i])
		if reply.Opcode == OpError {
			err = errors.New(string(reply.Args[0]))
		}
	}

	if err != nil {
		cp.releaseConn()
	}
	return
}

func (cp *ConnPair) releaseConn() {
	for j := 0; j < FullReplFactor; j++ {
		if cp.followers[j] != nil {
			cp.followers[j].Close()
		}
	}
}

type FollowersConnPool struct {
	followerAddr [FullReplFactor]string
	head         *ConnPair
	count        int
	lock         *sync.Mutex
}

func NewFollowersConnPool() *FollowersConnPool {
	fcp := new(FollowersConnPool)
	fcp.lock = new(sync.Mutex)
	return fcp
}

func (fcp *FollowersConnPool) getConnPair() (cp *ConnPair) {
	fcp.lock.Lock()
	if fcp.head != nil {
		cp, fcp.head = fcp.head, fcp.head.next
		cp.next = nil
		fcp.count--
	}
	fcp.lock.Unlock()

	if cp != nil {
		return
	}

	//set up a new conn pair
	cp = new(ConnPair)
	for i := 0; i < FullReplFactor; i++ {
		rc, err := net.Dial("tcp", fcp.followerAddr[i])
		if err != nil {
			cp.releaseConn()
			cp = nil
			break
		}
		cp.followers[i], _ = rc.(*net.TCPConn)
		cp.followers[i].SetKeepAlive(true)
		cp.followers[i].SetNoDelay(true)
	}
	return
}

func (fcp *FollowersConnPool) putConnPair(cp *ConnPair) {
	fcp.lock.Lock()
	if fcp.count < MaxFollowerConnPair {
		cp.next = fcp.head
		fcp.head = cp
		fcp.count++
	} else {
		cp.releaseConn()
	}

	fcp.lock.Unlock()
}

func (fcp *FollowersConnPool) flushAll() {
	fcp.lock.Lock()
	for cp := fcp.head; cp != nil; cp = cp.next {
		cp.releaseConn()
	}
	fcp.head = nil
	fcp.lock.Unlock()
}
